package ie.flax.flaxengine.client;
import ie.flax.flaxengine.client.Graphic.FCamera;
import ie.flax.flaxengine.client.Graphic.Graphic;
import ie.flax.flaxengine.client.events.EventBus;
import ie.flax.flaxengine.client.events.ImageSelectionEvent;
import ie.flax.flaxengine.client.events.ImageSelectionEvent.Identifier;
import ie.flax.flaxengine.client.events.onFileLoadedEvent;
import ie.flax.flaxengine.client.events.onFileLoadedEventHandler;
import ie.flax.flaxengine.client.exception.MapDataCorrupt;
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.canvas.client.Canvas;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.ImageElement;
import com.google.gwt.event.dom.client.LoadEvent;
import com.google.gwt.event.dom.client.LoadHandler;
import com.google.gwt.user.client.Window;
import com.kfuntak.gwt.json.serialization.client.JsonSerializable;
import com.kfuntak.gwt.json.serialization.client.Serializer;
/**
* FMap is an automatic class, which given a filePath to a JSON map file will
* construct itself by magic. FMap does all the work for you.
* <br><br>
*
* The map object is basically the environment, it defines the tiles, the
* objects, the entity in the world. It is a very automated object in that it
* takes 1 parameter for its constructor. The path to the xml file which
* contains all the information about the map. Below is a template example of
* the information.
* <br><br>
* The map file is the game information file really. It contains almost all of
* the info about the different objects that will be created. The images for
* each object are also loaded when this map file is read. The tiles are all
* stored on one sheet and objects and entitys have their own sperate images due
* to the fact they may have animations.
* <br><br>
* Thought if a developer would like to programmatically add in all the objects
* and entitys they can do so. Though the map object requires the xml file for
* at-lest the tiles of the map.
*
*
* @author Ciaran McCann
*
*/
public class FMap implements JsonSerializable, onFileLoadedEventHandler{
private int width;
private int height;
private int tileSize;
private String name;
private transient boolean Loaded;
private ImageElement tileSheetImage;
/**
* This holds the string which is used to reference the tileSheet image in the imageLibary
*/
private String tileSheet;
/**
* Tiles are stored in this list and have relative corrdinates. IE (1,0) (2,0) .... (3,3)
* The relative unit it TILES
*/
private List<FTile> tiles = new ArrayList<FTile>();
private List<FObject> objects = new ArrayList<FObject>();
private List<FEntity> entities = new ArrayList<FEntity>();
/**
* FMap object is constructed from a file by provding the path name.
* @param mapPath - URL to map.json file
*/
public FMap(final String mapPath) {
name = mapPath;
EventBus.handlerManager.addHandler(onFileLoadedEvent.TYPE, this); //Register the obj for onFileLoaded events
FileHandle.readFileAsString(mapPath, this.toString());//Makes a request for the map file
}
/**
* This calls all the draw methods of the entities in the FMap, the tiles and checks weather they are on-screen and
* if they are they are then drawn to the screen.
* @param canvas
* @param cam2
*/
public void draw(
final FCamera cam,
final Canvas drawingSpace,
final double deltaTime //Ok this shouldn't be here, need to have a thing about how going to do update and drawing.
//No piont looping though all the elements twice for update and draw, so why not at same time, but anyway need to sleep on it
)
{
/**
* The below calucates and objects referencing is all done outside the loops to speed up the drawing
* TODO Ciaran - at some piont slim down these varibles. Wont save much but could be done some time
*/
final double camX = cam.getX();
final double camY = cam.getY();
final double camXWidth = camX+cam.getWidth();
final double camYHeight = camY+cam.getHeight();
int camXRelative = (int) (camX/tileSize);//plus one gives a border tile
int camYRelative = (int) (camY/tileSize);
final int camXAndWidth = (int) ( (camXRelative)+cam.getWidth()/tileSize ) + 1;
final int camYAndHeight = (int) ( (camYRelative)+cam.getHeight()/tileSize ) + 1;
final int camXRelativeCopy = camXRelative;
final int camYRelativeCopy = camYRelative;
final int camXRelativeCopyScaled = camXRelativeCopy*tileSize;
final int camYRelativeCopyScaled = camYRelativeCopy*tileSize;
final int totalTiles = tiles.size();
final Context2d ctx = drawingSpace.getContext2d();
FTile t = null;
/**
* currentYValue varibles is here to save on a multication and a subtraction every row item ( x corrdinate or coloum).
*/
int currentYValue = 0;
/**
* Again like the currentYValue this varible exists to remove a multication for every row item
*/
int currentYindexValue = 0;
ctx.save();
ctx.translate(cam.getInterpolation().x, cam.getInterpolation().y);
// all in tiles - relative
while(camYRelative <= camYAndHeight)
{
/**
* -1 to allow for the panning, read large comment below
*/
currentYValue = ( (camYRelative-1)*tileSize - camYRelativeCopyScaled ); // get next Y value
currentYindexValue = (camYRelative) * width; // get the next Y index value
// in number of tiles - relatived
while( camXRelative <= camXAndWidth)
{
if (camXRelative+currentYindexValue >= totalTiles) break;
t = tiles.get(camXRelative + currentYindexValue );
/**
* below is the call for the image to be drawn, note the -1 on the camXRelative, this is to facilate the panning.
* Basically it makes the map render with -1 positon and thus there will always a border tile colum, so when the camera is panning
* you don't notice anything wiered.
*
* I'm sure there is a better way to do this, tho it will do for the mo
*
*/
ctx.drawImage(tileSheetImage, t.getTextureX(), t.getTextureY(), tileSize, tileSize, ( (camXRelative-1)*tileSize - camXRelativeCopyScaled ) , currentYValue, tileSize, tileSize);
camXRelative++;
}
camXRelative = camXRelativeCopy; // rest the X to intial
camYRelative++;
}
ctx.restore();
for(FObject temp : objects)
{
//check if the eneity can be seen on screen before drawing
if(temp.getX() >= camX-tileSize && temp.getX() <= camXWidth &&temp.getY() >= camY-tileSize && temp.getY() <= camYHeight)
{
if(deltaTime!=-1)temp.update(deltaTime);
temp.draw(drawingSpace);
}
}
for(FEntity temp : entities)
{
//check if the eneity can be seen on screen before drawing
if(temp.getX() >= camX-temp.getWidth() && temp.getX() <= camXWidth &&temp.getY() >= camY-temp.getHeight() && temp.getY() <= camYHeight)
{
if(deltaTime!=-1)temp.update(deltaTime);
temp.draw(drawingSpace);
}
}
}
/**
* Gets the tile at the location which was clicked
*
* @param xClick - absolute click values of the mouse
* @param yClick - absolute click values of the mouse
* @return
*/
public final FTile getTile(int xClick, int yClick)
{
/**
* Both the camera and click values are absolute pixel values
* adding them togtheier and divding them by the tilesize and then dropping the decimal
* will get you the tile x and y such as (2,2)
*/
int clickX = (int) (xClick/tileSize);
int clickY = (int) (yClick/tileSize);
clickX += (int) FlaxEngine.camera.getX()/tileSize;
clickY += (int) +FlaxEngine.camera.getY()/tileSize;
/**
* Read the comment in the draw method, you will then understand why there is a +1 here
*/
int index = (clickX+1) + ((clickY+1)*width);
if(index < 0)
index = 0;
return tiles.get( index );
}
/**
* If true this FMap object has finished loading its data
* @return
*/
public final boolean getLoaded() {
return Loaded;
}
/**
* Pass this method JSON and it gives you back an FMap object which you can
* then assign to your object via FMap myMap = JsonToFMap(String Json);
*
* @param JSON
* @return
* @throws MapDataCorrupt - if the JSON map string passed in has an error in it this expection will be throw
*/
public static final FMap fromJson(String Json) throws MapDataCorrupt {
/**
* TODO : http://code.google.com/p/gwt-lzma/
*/
FMap temp = null;
try {
Serializer serializer = (Serializer) GWT.create(Serializer.class);
temp = (FMap) serializer.deSerialize(Json,"ie.flax.flaxengine.client.FMap");
} catch (Exception e) {
throw new MapDataCorrupt();
}
return temp;
}
/**
* Creates a JSON string from the current FMap object
*
* @return String of JSON
*/
public static final String toJson(FMap map) {
Serializer serializer = (Serializer) GWT.create(Serializer.class);
return serializer.serialize(map);
}
/**
* basiclly does the following operation on the object
* FMap x = new ("map.json);
* FMap y = x; //For some reaosn doing this does not work
* <br><br>
* This method replace the current object with another
* @param newMapObj
*/
public void replaceMap(FMap newMapObj)
{
/**
* It would be nicer to go this =
* FMap.JsonToFMap(e.getDataLoadedFromFile()); though we can't do
* that. It would have to be outside the class which wouldn't work as
* well with the ID's etc.
*/
this.entities = newMapObj.entities;
this.tiles = newMapObj.tiles;
this.objects = newMapObj.objects;
this.tileSheet = newMapObj.tileSheet;
this.tileSize = newMapObj.tileSize;
this.width = newMapObj.width;
this.height = newMapObj.height;
/**
* Code to generate a map, very handy
*/
// int x =0;
// int y =0;
//
// while ( y < height)
// {
// while( x < width)
// {
// tiles.add( new FTile( 21, Graphic.getSingleton().getImage(tileSheet), tileSize));
// x++;
// }
//
// x = 0;
//
// y++;
// }
}
/**
* This method is run when a onFileLoaded event is fired. It then checks was
* the file loaded request by itself. Which it does so by the ID. It then constructs the object
*
* @param e
* event object
*/
@Override
public void onFileLoaded(onFileLoadedEvent e) {
//Checks it was this object that requested the file
if(this.toString().equalsIgnoreCase(e.getId()))
{
/**
* Creates a temp FMap object from the JSON string which is stored
* in the event object which was pulled from the server
*/
FMap temp = null;
try {
temp = fromJson(e.getDataLoadedFromFile());
} catch (MapDataCorrupt e1) {
Window.alert(e1.getError());
}
final FMap temp2 = temp;
/**
* Loads the tilesheet of the map, waits for the onLoad call back and fires a ImageSelection
* event which will load the tilesheet into the tileMenuView
*/
Graphic.getSingleton().loadImage(temp.getTileSheet()).addLoadHanderl( new LoadHandler() {
@Override
public void onLoad(LoadEvent event) {
/**
* Only load in the new map data once all the images have loaded for the map.
* So that the calucations for which texture to pick from an image can be done at load and not during frame
*/
replaceMap(temp2); //op code : this = temp;
EventBus.handlerManager.fireEvent(new ImageSelectionEvent(tileSheet, Identifier.TILE_SHEET));
// Player p = new Player(new FVector(300, 300));
// p.attachCamera(FlaxEngine.camera);
// addEntity(p);
}
});
FLog.info("An FMap object of name [" + this.name + "]; was constructed from a file sucessfully");
Loaded = true;
}
}
/**
* This adds the given FEntity object or objects that are derived from FEntity
* to the map object.
* @param entity
*/
public void addEntity(FEntity entity)
{
if(entity.getX() >= 0 && entity.getX() <= width*tileSize+tileSize&&entity.getY() >= 0&&entity.getY() <= height*tileSize-tileSize)
{
entities.add(entity);
FLog.trace(entity + " was created and added to " + this);
}else
{
FLog.warn("Unable to add " + entity + " to " + this);
}
}
/**
* This adds the given FObject object or objects that are derived from FObject
* to the map object.
* @param FObject
*/
public void addObjects(FObject object)
{
objects.add(object);
}
/**
* This allows the user to select and modify the enitiy
* @param id
* @return
*/
public FEntity getEntity(int id)
{
return entities.get(id);
}
/**
* This allows the user to select and modify the enitiy
* @param id
* @return
*/
public FEntity getEntity(int x, int y)
{
return entities.get(0); //TODO: fix this
}
/**
* DO NOT USE THIS Constructor -This method only exist so that JSON serialization
* can work Using this method is at your own risk and will most likely break
* your code in RUNTIME!!
*
*/
@Deprecated
public FMap() {
}
/**
* Gets the name of the map file which this map object was created from
* @return String name of map
*/
public String getName() {
return name;
}
/**
* Sets the tileSize of the map
* @param tileSize
*/
public void setTileSize(int tileSize) {
this.tileSize = tileSize;
}
/**
* Gets the tileSize used by the current map
* @return int tilesize
*/
public int getTileSize() {
return tileSize;
}
/**
* Sets the tileSheet of the engine and also sets the FMaps tilesheet ImageEelement to that message
* @param tileSheet
*/
public void setTileSheet(String tileSheet) {
this.tileSheet = tileSheet;
this.tileSheetImage = Graphic.getSingleton().getImage(tileSheet);
}
/**
*
* @return width (int)
*/
public int getWidth() {
return width;
}
/**
*
* @return height (int)
*/
public int getHeight() {
return height;
}
/**
* This is used to reference the imageLibary with the name of the tileSheet
* eg. imageLibary.get("myTileSheet")
*
* @return tileSheet name
*/
public String getTileSheet() {
return tileSheet;
}
/**
* DO NOT USE THIS METHOD -This method only exist so that JSON serialization
* can work Using this method is at your own risk and will most likely break
* your code in RUNTIME!!
*
*/
@Deprecated
public void setName(String name) {
this.name = name;
}
/**
* DO NOT USE THIS METHOD -This method only exist so that JSON serialization
* can work Using this method is at your own risk and will most likely break
* your code in RUNTIME!!
*
*/
@Deprecated
public List<FTile> getTiles() {
return tiles;
}
/**
* DO NOT USE THIS METHOD -This method only exist so that JSON serialization
* can work Using this method is at your own risk and will most likely break
* your code in RUNTIME!!
*
*/
@Deprecated
public void setObjects(List<FObject> objects) {
this.objects = objects;
}
/**
* DO NOT USE THIS METHOD -This method only exist so that JSON serialization
* can work Using this method is at your own risk and will most likely break
* your code in RUNTIME!!
*
*/
@Deprecated
public List<FObject> getObjects() {
return objects;
}
/**
* DO NOT USE THIS METHOD -This method only exist so that JSON serialization
* can work Using this method is at your own risk and will most likely break
* your code in RUNTIME!!
*
*/
@Deprecated
public void setWidth(int width) {
this.width = width;
FlaxEngine.camera.setMapWidth(width);
}
/**
* DO NOT USE THIS METHOD -This method only exist so that JSON serialization
* can work Using this method is at your own risk and will most likely break
* your code in RUNTIME!!
*
*/
@Deprecated
public void setHeight(int height) {
this.height = height;
FlaxEngine.camera.setMapHeight(height);
}
/**
* DO NOT USE THIS METHOD -This method only exist so that JSON serialization
* can work Using this method is at your own risk and will most likely break
* your code in RUNTIME!!
*
*/
@Deprecated
public void setTiles(List<FTile> tiles) {
this.tiles = tiles;
}
/**
* DO NOT USE THIS METHOD -This method only exist so that JSON serialization
* can work Using this method is at your own risk and will most likely break
* your code in RUNTIME!!
*
*/
@Deprecated
public List<FEntity> getEntities() {
return entities;
}
/**
* DO NOT USE THIS METHOD -This method only exist so that JSON serialization
* can work Using this method is at your own risk and will most likely break
* your code in RUNTIME!!
*
*/
@Deprecated
public void setEntities(List<FEntity> entities) {
this.entities = entities;
}
/**
* DO NOT USE THIS METHOD -This method only exist so that JSON serialization
* can work Using this method is at your own risk and will most likely break
* your code in RUNTIME!!
*
*/
@Deprecated
public ImageElement getTileSheetImage() {
return tileSheetImage;
}
/**
* DO NOT USE THIS METHOD -This method only exist so that JSON serialization
* can work Using this method is at your own risk and will most likely break
* your code in RUNTIME!!
*
*/
@Deprecated
public void setTileSheetImage(ImageElement tileSheetImage) {
this.tileSheetImage = tileSheetImage;
}
}